package com.fox.stock.service.impl;

import com.fox.stock.constant.ParseType;
import com.fox.stock.face.StockCacheFace;
import com.fox.stock.face.impl.StockCacheFaceImpl;
import com.fox.stock.mapper.*;
import com.fox.stock.pojo.entity.StockBlockRtInfo;
import com.fox.stock.pojo.entity.StockMarketIndexInfo;
import com.fox.stock.pojo.entity.StockOuterMarketIndexInfo;
import com.fox.stock.pojo.entity.StockRtInfo;
import com.fox.stock.pojo.vo.StockInfoConfig;
import com.fox.stock.service.StockTimeTaskService;
import com.fox.stock.utils.DateTimeUtil;
import com.fox.stock.utils.IdWorker;
import com.fox.stock.utils.ParserStockInfoUtil;
import com.google.common.collect.Lists;

import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@Service
public class StockTimeTaskServiceImpl implements StockTimeTaskService {
    /**
     * 封装了常用的配置类(marketUrl等)
     */
    @Autowired
    private StockInfoConfig stockInfoConfig;

    /**
     * restTemplate类,用于采集股票数据
     */
    @Autowired
    private RestTemplate restTemplate;
    /**
     * 雪花算法获取id
     */
    @Autowired
    private IdWorker idWorker;

    @Autowired
    private StockBusinessMapper stockBusinessMapper;

    @Autowired
    private StockMarketIndexInfoMapper stockMarketIndexInfoMapper;

    @Autowired
    private ParserStockInfoUtil parserStockInfoUtil;

    @Autowired
    private StockRtInfoMapper stockRtInfoMapper;

    @Autowired
    private StockBlockRtInfoMapper stockBlockRtInfoMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private StockOuterMarketIndexInfoMapper stockOuterMarketIndexInfoMapper;

    @Autowired
    private StockCacheFaceImpl stockCacheFace;
    @Autowired
    private StockCacheFaceImpl stockCacheFaceImpl;

    /**
     * 获取国内大盘数据
     */
    @Override
    public void getInnerMarketInfo() {
        // 1. 阶段一: 采集原始数据
        // 1.1 封装url:
        String url = stockInfoConfig.getMarketUrl() + String.join(",",stockInfoConfig.getInner());
        // 1.2 封装请求头
        HttpHeaders headers = new HttpHeaders();
        headers.add("Referer","https://finance.sina.com.cn/stock/"); // 添加Referer请求头, 用于采集新浪的股票数据
        headers.add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 SLBrowser/9.0.6.5061 SLBChan/112 SLBVPV/64-bit");// 添加说明用户端设备请求
        // 1.3 封装请求对象
        HttpEntity<Object> entity = new HttpEntity<>(headers);
        // 1.4 发送请求到url
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        // 1.5 判断请求是否成功(成功: 继续 , 失败: 终止)
        if (response.getStatusCodeValue() != 200){
            log.info("当前时间:{},请求失败,请求状态码为:{}", DateTime.now(), response.getStatusCodeValue());
            return;
        }
        String res = response.getBody();
        log.info("当前时间:{},采集的数据内容:{}", DateTime.now(), res);

        // 2. 阶段二: 利用正则表达式解析数据
        // 2.1 定义正则表达式
        String reg = "var hq_str_(.+)=\"(.+)\";";
        // 2.2 编译表达式获取编译对象
        Pattern pattern = Pattern.compile(reg);
        // 2.3 匹配字符串
        Matcher matcher = pattern.matcher(res);
        // 2.4 获取各个匹配的数据
        List<StockMarketIndexInfo> list = new ArrayList<>();// 用于存储封装后的对象
        while(matcher.find()){
            // 大盘编码
            String marketCode = matcher.group(1);
            // 其他信息
            String otherInfo = matcher.group(2);
            // 以逗号截取字符串,获取分割其他信息
            String[] splitArr = otherInfo.split(",");
            //大盘名称
            String marketName=splitArr[0];
            //获取当前大盘的开盘点数
            BigDecimal openPoint=new BigDecimal(splitArr[1]);
            //前收盘点
            BigDecimal preClosePoint=new BigDecimal(splitArr[2]);
            //获取大盘的当前点数
            BigDecimal curPoint=new BigDecimal(splitArr[3]);
            //获取大盘最高点
            BigDecimal maxPoint=new BigDecimal(splitArr[4]);
            //获取大盘的最低点
            BigDecimal minPoint=new BigDecimal(splitArr[5]);
            //获取成交量
            Long tradeAmt=Long.valueOf(splitArr[8]);
            //获取成交金额
            BigDecimal tradeVol=new BigDecimal(splitArr[9]);
            //时间
            Date curTime = DateTimeUtil.getDateTimeWithoutSecond(splitArr[30] + " " + splitArr[31]).toDate();
            // 3. 阶段三: 封装解析后的数据成实体类(entity)
            // 3.1 组装entity对象
            StockMarketIndexInfo info = StockMarketIndexInfo.builder()
                    .id(idWorker.nextId())
                    .marketCode(marketCode)
                    .marketName(marketName)
                    .openPoint(openPoint)
                    .preClosePoint(preClosePoint)
                    .curPoint(curPoint)
                    .maxPoint(maxPoint)
                    .minPoint(minPoint)
                    .tradeAmount(tradeAmt)
                    .tradeVolume(tradeVol)
                    .curTime(curTime)
                    .build();
            list.add(info);

        }
        log.info("采集的大盘数据:{}", list);

        // 4. 阶段四: 利用mybatis批量将实体类入库
        int count = stockMarketIndexInfoMapper.insertBatch(list);
        if (count > 0){
            // TODO: 入库成功后应该提醒backend工程更新数据(利用rabbitMq发送消息队列让backend接收)
            // 这里传入当前时间可以计算处延迟的时间,以便发现错误
            rabbitTemplate.convertAndSend("stockExchange","inner.market", new Date());
            log.info("插入数据成功:{}条",count);
        }else {
            log.info("当前时间:{},插入数据失败",DateTime.now());
        }

    }

    /**
     * 获取个股信息
     */
    public void getStockRtIndex(){
        // 获取股票的编码集合 (后期优化: 将股票id数据存入redis缓存)
        List<String> infos = stockCacheFaceImpl.getAllStockCodeWithPredix();
//        // 获取所有个股编号并分批发起请求
//        List<String> infos = stockBusinessMapper.getAllStockCode();
//        infos = infos.stream().map(code -> code.startsWith("6") ? "sh" + code : "sz" + code).collect(Collectors.toList());
        long startTime = System.currentTimeMillis();
        Lists.partition(infos,15).forEach(s -> {
/*           // 原始方案(2840ms)

            // 封装每一批的url地址
            String url = stockInfoConfig.getMarketUrl() + String.join(",",s);
            // 封装请求头
            HttpHeaders headers = new HttpHeaders();
            headers.add("Referer","https://finance.sina.com.cn/stock/"); // 添加Referer请求头, 用于采集新浪的股票数据
            headers.add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 SLBrowser/9.0.6.5061 SLBChan/112 SLBVPV/64-bit");// 添加说明用户端设备请求
            // 封装请求对象
            HttpEntity<Object> entity = new HttpEntity<>(headers);
            // 发送请求获取个股的信息
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
            // 判断请求是否成功(成功: 继续 , 失败: 终止)
            if (response.getStatusCodeValue() != 200){
                log.info("当前时间:{},请求失败,请求状态码为:{}", DateTime.now(), response.getStatusCodeValue());
                return;
            }
            // 成功,获取响应的个股信息数据
            String Data = response.getBody();
            // 继续利用工具类解析
            List<StockRtInfo> list = parserStockInfoUtil.parser4StockOrMarketInfo(Data, ParseType.ASHARE);
            log.info("分批{}个数据:{}",list.size(),list);

            // 解析后的批量入数据库
            int count = stockRtInfoMapper.insertBatch(list);
            if (count > 0){
                log.info("插入数据成功:{}条",count);
            }else {
                log.info("当前时间:{},插入数据失败",DateTime.now());
            }*/



            // 方案1: 引入线程池(优化)

            threadPoolTaskExecutor.execute(() -> {
                // 封装每一批的url地址
                String url = stockInfoConfig.getMarketUrl() + String.join(",",s);
                // 封装请求头
                HttpHeaders headers = new HttpHeaders();
                headers.add("Referer","https://finance.sina.com.cn/stock/"); // 添加Referer请求头, 用于采集新浪的股票数据
                headers.add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 SLBrowser/9.0.6.5061 SLBChan/112 SLBVPV/64-bit");// 添加说明用户端设备请求
                // 封装请求对象
                HttpEntity<Object> entity = new HttpEntity<>(headers);
                // 发送请求获取个股的信息
                ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
                // 判断请求是否成功(成功: 继续 , 失败: 终止)
                if (response.getStatusCodeValue() != 200){
                    log.info("当前时间:{},请求失败,请求状态码为:{}", DateTime.now(), response.getStatusCodeValue());
                    return;
                }
                // 成功,获取响应的个股信息数据
                String Data = response.getBody();
                // 继续利用工具类解析
                List<StockRtInfo> list = parserStockInfoUtil.parser4StockOrMarketInfo(Data, ParseType.ASHARE);
                log.info("分批{}个数据:{}",list.size(),list);

                // 解析后的批量入数据库
                int count = stockRtInfoMapper.insertBatch(list);
                if (count > 0){
                    log.info("插入数据成功:{}条",count);
                }else {
                    log.info("当前时间:{},插入数据失败",DateTime.now());
                }
            });
        });
        long time = System.currentTimeMillis() - startTime;
        log.info("本次采集时长为: {}ms",time);
    }



    /**
     * 获取板块信息
     */
    @Override
    public void getBlockRtInfo() {
        // 组装url和请求对象
        // url
        String url = stockInfoConfig.getBlockUrl();
        // 请求对象
        // 封装请求头
        HttpHeaders headers = new HttpHeaders();
        headers.add("Referer","https://finance.sina.com.cn/stock/"); // 添加Referer请求头, 用于采集新浪的股票数据
        headers.add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 SLBrowser/9.0.6.5061 SLBChan/112 SLBVPV/64-bit");// 添加说明用户端设备请求
        // 封装请求对象
        HttpEntity<Object> entity = new HttpEntity<>(headers);
        // 发送请求获取板块json信息
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        // 判断请求是否成功(成功: 继续 , 失败: 终止)
        if (response.getStatusCodeValue() != 200){
            log.info("当前时间:{},请求失败,请求状态码为:{}", DateTime.now(), response.getStatusCodeValue());
            return;
        }
        // 成功,获取响应体中的板块信息数据
        String Data = response.getBody();
/*        // 这里的data是xxx = {xx:xx,xx:xx} 如果直接用Data解析就会出错 只需要大括号里的内容就可以了
        if(Data == null){
            log.info("数据为空");
            return;

        }
        Data = Data.substring(Data.indexOf("=") + 1);
        Gson gson = new Gson();
        HashMap map = gson.fromJson(Data, HashMap.class);
        Collection values = map.values();
        // 后面遍历封装,懒得写了*/
        // 利用工具类解析板块信息
        List<StockBlockRtInfo> list = parserStockInfoUtil.parse4StockBlock(Data);
        // 批量入库
        int count = stockBlockRtInfoMapper.insertBatch(list);
        if (count > 0){
            // TODO: 入库成功后应该提醒backend工程更新数据(利用rabbitMq发送消息队列让backend接收)
            // 这里传入当前时间可以计算处延迟的时间,以便发现错误
            rabbitTemplate.convertAndSend("BlockExchange","block.rt", new Date());


            log.info("插入数据成功:{}条",count);
        }else {
            log.info("当前时间:{},插入数据失败",DateTime.now());
        }
    }

    @Override
    public void getOutMarketInfo() {
        // 组装url和请求对象
        // url : http://hq.sinajs.cn/list=int_dji,int_nasdaq,int_hangseng,int_nikkei,b_FSSTI
        String url = stockInfoConfig.getMarketUrl() + String.join(",",stockInfoConfig.getOuter());
        // 请求对象
        // 封装请求头
        HttpHeaders headers = new HttpHeaders();
        headers.add("Referer","https://finance.sina.com.cn/stock/"); // 添加Referer请求头, 用于采集新浪的股票数据
        headers.add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 SLBrowser/9.0.6.5061 SLBChan/112 SLBVPV/64-bit");// 添加说明用户端设备请求
        // 封装请求对象
        HttpEntity<Object> entity = new HttpEntity<>(headers);
        // 发送请求获取板块json信息
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        // 判断请求是否成功(成功: 继续 , 失败: 终止)
        if (response.getStatusCodeValue() != 200){
            log.info("当前时间:{},请求失败,请求状态码为:{}", DateTime.now(), response.getStatusCodeValue());
            return;
        }
        // 成功,获取响应体中的板块信息数据
        String Data = response.getBody();
        log.info("当前时间:{},采集的数据内容:{}", DateTime.now(), Data);

        // 利用工具类解析 1. 大盘   2. 外盘   3.A股
        List<StockOuterMarketIndexInfo> list = parserStockInfoUtil.parser4StockOrMarketInfo(Data, ParseType.OUTER);

        // 获取到集合 利用mybatis批量入库
        int count = stockOuterMarketIndexInfoMapper.insertBatch(list);
        if (count > 0){
            // TODO: 入库成功后应该提醒backend工程更新数据(利用rabbitMq发送消息队列让backend接收)
            // 这里传入当前时间可以计算处延迟的时间,以便发现错误
            rabbitTemplate.convertAndSend("BlockExchange","block.rt", new Date());


            log.info("插入数据成功:{}条",count);
        }else {
            log.info("当前时间:{},插入数据失败",DateTime.now());
        }

    }

}
